package com.geccocrawler.gecco.dynamic;

import com.geccocrawler.gecco.annotation.Gecco;
import javassist.*;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.*;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 动态生成SpiderBean，支持类，属性和注解全部动态生成，也支持只动态生成注解
 *
 * @author huchengyi
 */
public class JavassistDynamicBean implements DynamicBean {

    private static Logger log = LoggerFactory.getLogger(JavassistDynamicBean.class);

    public static final String HtmlBean = "html";

    public static final String JsonBean = "json";

    private static ClassPool pool;

    static {
        pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(JavassistDynamicBean.class));
    }

    private CtClass clazz;

    private ClassFile cfile;

    private ConstPool cpool;

    public JavassistDynamicBean(String spiderBeanName) {
        try {
            clazz = pool.get(spiderBeanName);
        } catch (NotFoundException e) {
            log.error(spiderBeanName + " not found");
        }
    }

    /**
     * 构造类
     *
     * @param spiderBeanName 名称
     * @param beanType       类型html/json
     */
    public JavassistDynamicBean(String spiderBeanName, String beanType) {
        try {
            clazz = pool.get(spiderBeanName);
        } catch (NotFoundException e) {
            log.debug(spiderBeanName + " not found, to be create!");
            try {
                clazz = pool.makeClass(spiderBeanName);
                if (beanType.equals(HtmlBean)) {
                    CtClass htmlBeanInterface = pool.get("com.geccocrawler.gecco.spider.HtmlBean");
                    clazz.addInterface(htmlBeanInterface);
                } else {
                    CtClass jsonBeanInterface = pool.get("com.geccocrawler.gecco.spider.JsonBean");
                    clazz.addInterface(jsonBeanInterface);
                }
            } catch (NotFoundException cex) {
                // create error
                log.error("create class " + spiderBeanName + " error.", cex);
            }
        }
        if (clazz.isFrozen()) {
            clazz.defrost();
            log.info(spiderBeanName + " is frozen");
        }
        cfile = clazz.getClassFile();
        cpool = cfile.getConstPool();
    }

    @Override
    public JavassistDynamicBean gecco(String matchUrl, String... pipelines) {
        gecco(new String[]{matchUrl}, pipelines);
        return this;
    }

    @Override
    public JavassistDynamicBean gecco(String[] matchUrl, String... pipelines) {
        gecco(matchUrl, "", 3000, pipelines);
        return this;
    }

    @Override
    public JavassistDynamicBean gecco(String matchUrl, String downloader, int timeout, String... pipelines) {
        return gecco(new String[]{matchUrl}, downloader, timeout, pipelines);
    }

    @Override
    public JavassistDynamicBean gecco(String[] matchUrl, String downloader, int timeout, String... pipelines) {
        return gecco(matchUrl, downloader, "", "", timeout, pipelines);
    }

    @Override
    public JavassistDynamicBean gecco(String matchUrl, String downloader, String assertion, String assertionRule, int timeout, String... pipelines) {
        return gecco(new String[]{matchUrl}, downloader, assertion, assertionRule, timeout, pipelines);
    }

    @Override
    public JavassistDynamicBean gecco(String[] matchUrl, String downloader, String assertion, String assertionRule, int timeout, String... pipelines) {
        return gecco(matchUrl, downloader, assertion, assertionRule, "", timeout, pipelines);
    }

    @Override
    public JavassistDynamicBean gecco(String matchUrl, String downloader, String assertion, String assertionRule, String downloadExhand, int timeout, String... pipelines) {
        return gecco(new String[]{matchUrl}, downloader, assertion, assertionRule, downloadExhand, timeout, pipelines);
    }

    @Override
    public JavassistDynamicBean gecco(String[] matchUrl, String downloader, String assertion, String assertionRule, String downloadExhand, int timeout, String... pipelines) {
        AnnotationsAttribute attr = new AnnotationsAttribute(cpool, AnnotationsAttribute.visibleTag);

        Annotation annot = new Annotation(Gecco.class.getName(), cpool);
        // matchUrl
        ArrayMemberValue arrayMemberValueMatchUrl = new ArrayMemberValue(cpool);
        MemberValue[] elementMatchUrls = new StringMemberValue[matchUrl.length];
        for (int i = 0; i < matchUrl.length; i++) {
            elementMatchUrls[i] = new StringMemberValue(matchUrl[i], cpool);
        }
        arrayMemberValueMatchUrl.setValue(elementMatchUrls);
        annot.addMemberValue("matchUrl", arrayMemberValueMatchUrl);


        // downloader
        annot.addMemberValue("downloader", new StringMemberValue(downloader, cpool));
        // timeout
        annot.addMemberValue("timeout", new IntegerMemberValue(cpool, timeout));
        // pipelines
        ArrayMemberValue arrayMemberValue = new ArrayMemberValue(cpool);
        MemberValue[] elements = new StringMemberValue[pipelines.length];
        for (int i = 0; i < pipelines.length; i++) {
            elements[i] = new StringMemberValue(pipelines[i], cpool);
        }
        arrayMemberValue.setValue(elements);
        annot.addMemberValue("pipelines", arrayMemberValue);

        //断言的动态生成，add by zhao.weiwei
        if (StringUtils.isNotEmpty(assertion))
            annot.addMemberValue("assertion", new StringMemberValue(assertion, cpool));
        if (StringUtils.isNotEmpty(assertionRule))
            annot.addMemberValue("assertionRule", new StringMemberValue(assertionRule, cpool));
        //下载异常处理的动态生成，add by zhao.weiwei
        if (StringUtils.isNotEmpty(downloadExhand))
            annot.addMemberValue("downloadExhand", new StringMemberValue(downloadExhand, cpool));
        attr.addAnnotation(annot);
        cfile.addAttribute(attr);
        return this;
    }


    @Override
    public DynamicBean removeField(String fieldName) {
        try {
            clazz.removeField(clazz.getField(fieldName));
            clazz.removeMethod(clazz.getDeclaredMethod("get" + StringUtils.capitalize(fieldName)));
            clazz.removeMethod(clazz.getDeclaredMethod("set" + StringUtils.capitalize(fieldName)));
        } catch (NotFoundException e) {
            e.printStackTrace();
            log.error("can't remove field : " + fieldName);
        }
        return this;
    }

    /**
     * 返回已经存在的属性
     */
    @Override
    public DynamicField existField(String fieldName) {
        return new JavassistDynamicField(this, clazz, cpool, fieldName);
    }

    /**
     * 由于有歧义，已经被existField代替
     */
    @Override
    @Deprecated
    public DynamicField field(String fieldName) {
        return existField(fieldName);
    }

    /**
     * 如果当前属性不存在先创建属性以及setter/getter方法和注解。 如果已经属性返回当前属性
     */
    @Override
    public DynamicField field(String fieldName, CtClass fieldType) {
        try {
            clazz.getField(fieldName);
        } catch (NotFoundException e) {
            try {
                CtField f = new CtField(fieldType, fieldName, clazz);
                clazz.addField(f);
                getter(fieldName, f);
                setter(fieldName, f);
            } catch (CannotCompileException ex) {
                ex.printStackTrace();
            }
        }
        return new JavassistDynamicField(this, clazz, cpool, fieldName);
    }

    @Override
    public DynamicField field(String fieldName, Class<?> fieldClass) {
        return field(fieldName, FieldType.type(fieldClass));
    }

    private void getter(String fieldName, CtField field) {
        try {
            CtMethod m = CtNewMethod.getter("get" + StringUtils.capitalize(fieldName), field);
            clazz.addMethod(m);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    private void setter(String fieldName, CtField field) {
        try {
            CtMethod m = CtNewMethod.setter("set" + StringUtils.capitalize(fieldName), field);
            clazz.addMethod(m);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    @Override
    public DynamicField stringField(String fieldName) {
        return field(fieldName, FieldType.stringType);
    }

    @Override
    public DynamicField intField(String fieldName) {
        return field(fieldName, FieldType.intType);
    }

    @Override
    public DynamicField longField(String fieldName) {
        return field(fieldName, FieldType.longType);
    }

    @Override
    public DynamicField floatField(String fieldName) {
        return field(fieldName, FieldType.floatType);
    }

    @Override
    public DynamicField doubleField(String fieldName) {
        return field(fieldName, FieldType.doubleType);
    }

    @Override
    public DynamicField requestField(String fieldName) {
        return field(fieldName, FieldType.requestType);
    }

    @Override
    public DynamicField responseField(String fieldName) {
        return field(fieldName, FieldType.responseType);
    }

    @Override
    public DynamicField listField(String fieldName, Class<?> memberClass) {
        return field(fieldName, FieldType.listType(memberClass.getName()));
    }

    @Override
    public Class<?> loadClass() {
        try {
            Class<?> loadClass = clazz.toClass(GeccoClassLoader.get(), null);
            log.debug("load class : " + clazz.getName());
            return loadClass;
        } catch (CannotCompileException e) {
            e.printStackTrace();
            log.error(clazz.getName() + " cannot compile," + e.getMessage());
            return null;
        }
    }

    @Override
    public Class<?> register() {
        Class<?> loadClass = loadClass();
        if (loadClass.getAnnotation(Gecco.class) != null) {
            GeccoClassLoader.get().addClass(loadClass.getName(), loadClass);
        }
        log.debug("register class : " + clazz.getName());
        return loadClass;
    }

    @Override
    public void unloadClass() {
        if (clazz != null) {
            clazz.detach();
        }
    }

    @Override
    public ConstPool getConstPool() {
        return cpool;
    }

}
